/**
 * 
 */
package org.rollinitiative.d20.entity.skills;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Hashtable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rollinitiative.d20.AdjustableValue;
import org.rollinitiative.d20.Adjustment;
import org.rollinitiative.d20.Adjustment.AdjustmentSource;
import org.rollinitiative.d20.entity.Ability;
import org.rollinitiative.d20.entity.AbilityListenerValue;
import org.rollinitiative.d20.entity.AbilityValue;

/**
 * This class manages a collection of skills associated to a specific Actor.
 * 
 * @author bebopjmm
 * 
 * @since incr-0.1
 */
public class SkillsCollection
{
    static final Log LOG = LogFactory.getLog(SkillsCollection.class);

    Hashtable<Skill, ManagedSkill> skillsTable;

    ArrayList<Skill> classSkills;

    int classSkillBonus = 0;


    /**
     * Instantiates a new skill collection with no managed skills or class skills.
     */
    public SkillsCollection()
    {
        skillsTable = new Hashtable<Skill, ManagedSkill>();
        classSkills = new ArrayList<Skill>();
    }


    /**
     * This method includes the designated skill as a class skill.
     * 
     * @param classSkill the Skill to be included in the set of class skills.
     */
    public void addClassSkill(Skill classSkill)
    {
        synchronized (classSkills) {
            if (!classSkills.contains(classSkill)) {
                classSkills.add(classSkill);
            }
        }
    }


    /**
     * This method returns the current skills managed in the collection.
     * 
     * @return array of skills managed in the collection
     */
    public Skill[] getSkills()
    {
        synchronized (skillsTable) {
            return skillsTable.keySet().toArray(new Skill[skillsTable.keySet().size()]);
        }
    }


    /**
     * This method returns the adjustable value for the designated skill.
     * 
     * @param skill
     * @return
     */
    public AdjustableValue getSkill(Skill skill)
    {
        if (skillsTable.containsKey(skill)) {
            return skillsTable.get(skill).skillValue;
        }
        else {
            return null;
        }
    }


    public boolean hasSkill(Skill skill)
    {
        synchronized (skillsTable) {
            return skillsTable.containsKey(skill);
        }
    }


    public boolean isClassSkill(Skill skill)
    {
        synchronized (classSkills) {
            return classSkills.contains(skill);
        }
    }


    /**
     * @return the classSkillBonus
     */
    public int getClassSkillBonus()
    {
        return classSkillBonus;
    }


    /**
     * @param classSkillBonus the classSkillBonus to set
     */
    public void setClassSkillBonus(int classSkillBonus)
    {
        this.classSkillBonus = classSkillBonus;
        LOG.debug("classSkillBonus set to: " + this.classSkillBonus);
    }


    public void init(Skill skill, EnumMap<Ability, AbilityValue> abilities)
    {
        ManagedSkill newSkill = new ManagedSkill(skill.getId(), abilities
                .get(skill.getKeyAbility()));
        synchronized (skillsTable) {
            if (skillsTable.containsKey(skill)) {
                LOG.warn("Ignoring attempt to initialize skill already known: " + skill.getName());
                return;
            }
            skillsTable.put(skill, newSkill);
        }

    }


    public void train(Skill skill, int points)
    {
        synchronized (skillsTable) {
            if (!skillsTable.containsKey(skill)) {
                LOG.warn("Cannot train in unknown skill, " + skill.getName()
                        + " invoke init() for this skill first.");
                return;
            }
            LOG.info("Adding " + points + " points to skill " + skill.getName());
            ManagedSkill mSkill = skillsTable.get(skill);
            Adjustment ranksAdj = mSkill.ranksAdjustment;
            int ranks = ranksAdj.getValue() + points;
            ranksAdj.setValue(ranks);

            // Check if class skill bonus has been applied.
            if (classSkills.contains(skill) && (mSkill.classSkillAdjustment == null)) {
                LOG.info("Adding class skill bonus");
                mSkill.setClassSkillAdjustment(classSkillBonus);
            }
        }
    }

    class ManagedSkill
    {
        public String skillID;

        public AbilityListenerValue skillValue;

        public Adjustment ranksAdjustment;

        public Adjustment classSkillAdjustment = null;


        public ManagedSkill(String skillID, AbilityValue keyAbility)
        {
            this.skillID = skillID;
            skillValue = new AbilityListenerValue(0, keyAbility);
            ranksAdjustment = new Adjustment(AdjustmentSource.INHERENT, 0, "Ranks");
            skillValue.addAdjustment(ranksAdjustment);
            LOG.debug("Ability adjusted score for initialized skill, " + this.skillID + " = "
                    + skillValue.getCurrent());
        }


        public boolean hasClassAdjustment()
        {
            return classSkillAdjustment != null;
        }


        public void setClassSkillAdjustment(int bonus)
        {
            classSkillAdjustment = new Adjustment(AdjustmentSource.INHERENT, bonus,
                    "ClassSkillBonus");
            skillValue.addAdjustment(classSkillAdjustment);
        }
    }
}
